package trading.bo;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import trading.dataentry.MonitoredStocks;
import trading.util.Constants;
import trading.util.IBAPI;
import trading.util.IBMsgIDGen;
import trading.util.IBMsgIDGen.IBMsgBean;
import trading.util.IBMsgIDGen.IBMsgType;
import trading.util.MarketTimeHelper;

import com.ib.client.Contract;
import com.ib.client.EClientSocket;
import com.ib.client.Order;

public class IBAccessorBO {

    private IBAPI ibAPI;

    public void setIbAPI(IBAPI ibAPI) {
        this.ibAPI = ibAPI;
    }

    private IBMsgIDGen ibMsgIDGen;

    public void setIbMsgIDGen(IBMsgIDGen ibMsgIDGen) {
        this.ibMsgIDGen = ibMsgIDGen;
    }
    
    public IBMsgIDGen getIbMsgIDGen() {
        return this.ibMsgIDGen;
    }

    private DBAccessorBO dbAccessBO;

    public void setDbAccessorBO(DBAccessorBO dbAccessorBO) {
        this.dbAccessBO = dbAccessorBO;
    }

    private MarketTimeHelper marketTimeHelper;

    public void setMarketTimeHelper(MarketTimeHelper marketTimeHelper) {
        this.marketTimeHelper = marketTimeHelper;
    }

    private final int CONNECTION_PORT = 4001;
    private final String STOCK_TYPE = "STK";
    private final String FUTURE_TYPE = "FUT";
    private final String OPTION_TYPE = "OPT";
    private final String FUTURE_EXCHANGE = "CFE";
    private final String OPTION_EXCHANGE = "CBOE";
    private final String SMART_EXCHANGE = "SMART";
    private final String DEFAULT_EXCHANGE = "ARCA";
    private final String US_CURRENCY = "USD";

    public boolean connect() {
        // connect to TWS
        EClientSocket client = ibAPI.getClient();
        Map<String, String> msgInfo = new HashMap<String, String>();
        IBMsgBean ibMsgBean = ibMsgIDGen.getNextBean(IBMsgType.CONNECTION,
                msgInfo);
        client.eConnect("", CONNECTION_PORT, 0);

        if (client.isConnected()) {
            return true;
        }
        return false;
    }

    public void reqLastStockTickPrice(String ticker) {
        Map<String, String> msgInfo = new HashMap<String, String>();
        msgInfo.put(Constants.TICKER, ticker);
        IBMsgBean ibMsgBean = ibMsgIDGen.getNextBean(IBMsgType.LATEST_PRICE,
                msgInfo);
        Contract contract = getDefaultStockContract(ticker, ibMsgBean.getId());
        EClientSocket client = ibAPI.getClient();
        client.reqRealTimeBars(ibMsgBean.getId(), contract, 5, "TRADES", true);
    }

    /**
     * Now this method is only used to establish connection, no real meaning
     * @param ticker
     */
    public void reqStockTickSnapshotPrice(String ticker) {
        Map<String, String> msgInfo = new HashMap<String, String>();
        msgInfo.put(Constants.TICKER, ticker);
        IBMsgBean ibMsgBean = ibMsgIDGen.getNextSnapshotBean(
                IBMsgType.CONNECTION, msgInfo);
        Contract contract = getDefaultStockContract(ticker, ibMsgBean.getId());
        EClientSocket client = ibAPI.getClient();
        client.reqMktData(ibMsgBean.getId(), contract, "", true);
    }

    public void cancelLastStockTickPrice() {
        Map<Integer, IBMsgBean> activeBeans = this.ibMsgIDGen.getActiveBeans();
        List<Integer> toBeReleasedBeans = new ArrayList<Integer>();
        for (Integer msgId : activeBeans.keySet()) {
            if (activeBeans.get(msgId).getMsgType() == IBMsgType.LATEST_PRICE) {
                EClientSocket client = ibAPI.getClient();
                client.cancelRealTimeBars(msgId);
                toBeReleasedBeans.add(msgId);
            }
        }
        for (Integer msgId : toBeReleasedBeans) {
            this.ibMsgIDGen.releaseActiveMsgBean(msgId);
        }
    }

    public void reqHistoricalLowPriceAndDate(String ticker) {
        Map<String, String> msgInfo = new HashMap<String, String>();
        msgInfo.put(Constants.TICKER, ticker);
        IBMsgBean ibMsgBean = ibMsgIDGen.getNextBean(
                IBMsgType.HISTORICAL_LOW_PRICE_DATE, msgInfo);
        Contract contract = getDefaultStockContract(ticker, ibMsgBean.getId());

        String endDateTimePattern = "yyyyMMdd HH:mm:ss z";
        SimpleDateFormat formatter = this.marketTimeHelper.getSimpleDateFormatterGMT(endDateTimePattern);
        String endDateTime = formatter.format(new Date());

        EClientSocket client = ibAPI.getClient();

        client.reqHistoricalData(ibMsgBean.getId(), contract, endDateTime,
                "1 Y", "1 day", "TRADES", 1, 2);
    }

    /**
     * The method is only used to establish connection to historical data farm
     * @param ticker
     */
    public void sendHistoricalDataRequest(String ticker) {
        Map<String, String> msgInfo = new HashMap<String, String>();
        msgInfo.put(Constants.TICKER, ticker);
        IBMsgBean ibMsgBean = ibMsgIDGen.getNextBean(
                IBMsgType.CONNECTION, msgInfo);
        Contract contract = getDefaultStockContract(ticker, ibMsgBean.getId());
        EClientSocket client = ibAPI.getClient();
        String endDateTimePattern = "yyyyMMdd HH:mm:ss z";
        SimpleDateFormat formatter = this.marketTimeHelper.getSimpleDateFormatterGMT(endDateTimePattern);
        String endDateTime = formatter.format(marketTimeHelper
                .getLastClosingDateObject());
        client.reqHistoricalData(ibMsgBean.getId(), contract, endDateTime,
                "30 S", "30 secs", "TRADES", 1, 2);
    }
    
    public void cancelHistoricalData(int taskID) {
        EClientSocket client = ibAPI.getClient();
        client.cancelHistoricalData(taskID);
    }
    /**
     * We use ticker price movement to check if the market sentiment is bull or
     * bear.
     * 
     * @param ticker
     */
    public void reqMarketStatus(String ticker) {
        MonitoredStocks monitoredStock = dbAccessBO
                .getMonitoredStockData(ticker);
        Map<String, String> msgInfo = new HashMap<String, String>();
        msgInfo.put(Constants.HISTORICAL_LOW_PRICE,
                monitoredStock.getHistoricalLow() + "");
        IBMsgBean ibMsgBean = ibMsgIDGen.getNextBean(IBMsgType.MARKET_STATUS,
                msgInfo);
        Contract contract = getDefaultStockContract(ticker, ibMsgBean.getId());

        String endDateTimePattern = "yyyyMMdd HH:mm:ss z";
        SimpleDateFormat formatter = this.marketTimeHelper.getSimpleDateFormatterGMT(endDateTimePattern);
        String endDateTime = formatter.format(new Date());

        EClientSocket client = ibAPI.getClient();

        client.reqHistoricalData(ibMsgBean.getId(), contract, endDateTime,
                "1 Y", "1 day", "TRADES", 1, 2);
    }

    public void reqLastCloseStockTickPrice(String ticker) {
        Map<String, String> msgInfo = new HashMap<String, String>();
        msgInfo.put(Constants.TICKER, ticker);
        IBMsgBean ibMsgBean = ibMsgIDGen.getNextBean(
                IBMsgType.LAST_CLOSE_PRICE, msgInfo);
        Contract contract = getDefaultStockContract(ticker, ibMsgBean.getId());
        EClientSocket client = ibAPI.getClient();
        String endDateTimePattern = "yyyyMMdd HH:mm:ss z";
        SimpleDateFormat formatter = this.marketTimeHelper.getSimpleDateFormatterGMT(endDateTimePattern);
        String endDateTime = formatter.format(marketTimeHelper
                .getLastClosingDateObject());
        System.out.println("Last closing time is : " + endDateTime);

        client.reqHistoricalData(ibMsgBean.getId(), contract, endDateTime,
                "30 S", "30 secs", "TRADES", 1, 2);
    }

    /**
     * create market order to short stocks
     * 
     * @param ticker
     * @param size
     */
    public void createShortSellingOrder(String ticker, int size) {
        int msgId = this.ibMsgIDGen.getNewMsgID();
        // Note that the order goes through SMART route regardless the primary
        // exchange being set.
        Contract contract = this.getDefaultStockContract(ticker, msgId);
        Order order = this.getDefaultShortOrder(size, msgId);
        EClientSocket client = ibAPI.getClient();
        client.placeOrder(msgId, contract, order);
    }

    public void createTrailStopOrder(String ticker, int size, double trailAmount, double stopPrice) {
        int msgId = this.ibMsgIDGen.getNewMsgID();
        // Note that the order goes through SMART route regardless the primary
        // exchange being set.
        Contract contract = this.getDefaultStockContract(ticker, msgId);
        Order order = this.getDefaultTrailingStopOrder(size, trailAmount, stopPrice, msgId);
        EClientSocket client = ibAPI.getClient();
        client.placeOrder(msgId, contract, order);
    }

    public void updateTrailStopOrder(int orderID, String ticker, int size,
            double trailAmount, double stopPrice) {
        // Note that the order goes through SMART route regardless the primary
        // exchange being set.
        Contract contract = this.getDefaultStockContract(ticker, orderID);
        Order order = this.getDefaultTrailingStopOrder(size, trailAmount, stopPrice, orderID);
        EClientSocket client = ibAPI.getClient();
        client.placeOrder(orderID, contract, order);
    }

    /**
     * create market order to buy stocks, used to close short position
     * 
     * @param ticker
     * @param size
     */
    public void createSTKMktBuyOrder(String ticker, int size) {
        int msgId = this.ibMsgIDGen.getNewMsgID();
        // Note that the order goes through SMART route regardless the primary
        // exchange being set.
        Contract contract = this.getDefaultStockContract(ticker, msgId);
        Order order = this.getDefaultMktBuyOrder(size, msgId);
        EClientSocket client = ibAPI.getClient();
        client.placeOrder(msgId, contract, order);
    }

    public void createOptionOrder(String action, int size, String orderType,
            String ticker, String expiry, double strike, String right) {
        int msgId = this.ibMsgIDGen.getNewMsgID();
        Contract contract = this.getDefaultOptionContract(ticker, expiry,
                strike, right, msgId);
        Order order = this.getOrder(action, size, orderType, false, msgId);
        EClientSocket client = ibAPI.getClient();
        client.placeOrder(msgId, contract, order);
    }

    public void createLmtOptionOrder(String action, int size, String orderType,
            String ticker, double lmtPrice, String expiry, double strike,
            String right) {
        int msgId = this.ibMsgIDGen.getNewMsgID();
        Contract contract = this.getDefaultOptionContract(ticker, expiry,
                strike, right, msgId);
        Order order = this.getDefaultLimitOrder(action, size, orderType, true,
                lmtPrice, msgId);
        EClientSocket client = ibAPI.getClient();
        client.placeOrder(msgId, contract, order);
    }

    public void reqAllOpenOrders() {
        EClientSocket client = ibAPI.getClient();
        client.reqAllOpenOrders();
    }

    public void cancelOpenOrder(int orderId) {
        EClientSocket client = ibAPI.getClient();
        client.cancelOrder(orderId);
    }

    public void reqAccountUpdates() {
        EClientSocket client = ibAPI.getClient();
        client.reqAccountUpdates(true, "");
    }

    /**
     * expiration is in the format of "yyyymm", e.g. 201203
     * 
     * @param expiration
     */
    public void reqLastCloseFuturePrice(String ticker, String expiration) {
        System.out.println("VIX future expiration : " + expiration);
        Map<String, String> msgInfo = new HashMap<String, String>();
        msgInfo.put(Constants.TICKER, ticker);
        IBMsgBean ibMsgBean = ibMsgIDGen.getNextBean(
                IBMsgType.LAST_CLOSE_FUTURE_PRICE, msgInfo);
        Contract contract = getDefaultFutureContract(ticker, expiration,
                ibMsgBean.getId());
        EClientSocket client = ibAPI.getClient();
        String endDateTimePattern = "yyyyMMdd HH:mm:ss z";
        SimpleDateFormat formatter = this.marketTimeHelper.getSimpleDateFormatterGMT(endDateTimePattern);
        String endDateTime = formatter.format(marketTimeHelper
                .getLastClosingDateObject());
        client.reqHistoricalData(ibMsgBean.getId(), contract, endDateTime,
                "30 S", "30 secs", "TRADES", 1, 2);
    }

    public void reqCurrentFuturePrice(String ticker, String expiration) {
        System.out.println("VIX future expiration : " + expiration);
        Map<String, String> msgInfo = new HashMap<String, String>();
        msgInfo.put(Constants.TICKER, ticker);
        IBMsgBean ibMsgBean = ibMsgIDGen.getNextBean(
                IBMsgType.CURRENT_FUTURE_PRICE, msgInfo);
        Contract contract = getDefaultFutureContract(ticker, expiration,
                ibMsgBean.getId());
        EClientSocket client = ibAPI.getClient();
        client.reqMktData(ibMsgBean.getId(), contract, "", false);
    }

    public void reqCurrentOptionPrice(String ticker, String expiration,
            double strikePrice, String right) {
        Map<String, String> msgInfo = new HashMap<String, String>();
        msgInfo.put(Constants.TICKER, ticker);
        IBMsgBean ibMsgBean = ibMsgIDGen.getNextBean(
                IBMsgType.CURRENT_OPTION_PRICE, msgInfo);
        Contract contract = getDefaultOptionContract(ticker, expiration,
                strikePrice, right, ibMsgBean.getId());
        EClientSocket client = ibAPI.getClient();
        client.reqMktData(ibMsgBean.getId(), contract, "", false);
    }

    private Contract getDefaultFutureContract(String ticker, String expiration,
            int id) {
        Contract contract = new Contract();
        contract.m_conId = id;
        contract.m_symbol = ticker;
        contract.m_expiry = expiration;
        contract.m_secType = FUTURE_TYPE;
        contract.m_strike = 0;
        contract.m_right = "";
        contract.m_multiplier = "";
        contract.m_exchange = SMART_EXCHANGE;
        contract.m_primaryExch = FUTURE_EXCHANGE;
        contract.m_currency = US_CURRENCY;
        contract.m_localSymbol = "";
        contract.m_includeExpired = false;
        contract.m_secIdType = "";
        contract.m_secId = "";
        return contract;
    }

    private Contract getDefaultStockContract(String ticker, int id) {
        Contract contract = new Contract();
        // In order to simplify, contract id is same as ticker id.
        contract.m_conId = id;
        contract.m_symbol = ticker;
        contract.m_secType = STOCK_TYPE;
        contract.m_expiry = "";
        contract.m_strike = 0;
        contract.m_right = "";
        contract.m_multiplier = "";
        contract.m_exchange = SMART_EXCHANGE;
        contract.m_primaryExch = DEFAULT_EXCHANGE;
        contract.m_currency = US_CURRENCY;
        contract.m_localSymbol = "";
        contract.m_includeExpired = false;
        contract.m_secIdType = "";
        contract.m_secId = "";
        return contract;
    }

    private Contract getDefaultOptionContract(String ticker, String expiry,
            double strike, String right, int id) {
        Contract contract = new Contract();
        contract.m_conId = id;
        contract.m_symbol = ticker;
        contract.m_secType = OPTION_TYPE;
        contract.m_expiry = expiry;
        contract.m_strike = strike;
        contract.m_right = right; // C or P
        contract.m_multiplier = "";
        contract.m_exchange = SMART_EXCHANGE;
        contract.m_primaryExch = OPTION_EXCHANGE;
        contract.m_currency = US_CURRENCY;
        contract.m_localSymbol = "";
        contract.m_includeExpired = false;
        contract.m_secIdType = "";
        contract.m_secId = "";
        return contract;
    }

    private Order getDefaultShortOrder(int size, int orderId) {
        Order order = new Order();
        order.m_action = "SELL";
        order.m_totalQuantity = size;
        order.m_orderType = "MKT";
        order.m_orderId = orderId;
        return order;
    }

    private Order getDefaultMktBuyOrder(int size, int orderId) {
        Order order = new Order();
        order.m_action = "BUY";
        order.m_totalQuantity = size;
        order.m_orderType = "MKT";
        order.m_orderId = orderId;
        return order;
    }

    private Order getDefaultTrailingStopOrder(int size, double trailAmount, double stopPrice, int orderId) {
        Order order = new Order();
        order.m_action = "BUY";
        order.m_totalQuantity = size;
        order.m_orderType = "TRAILLIMIT";
        order.m_auxPrice = trailAmount;
        order.m_tif = "GTC";
        order.m_triggerMethod = 2;
        order.m_trailStopPrice = stopPrice;
        order.m_lmtPrice = stopPrice + 0.01;
        order.m_orderId = orderId;
        return order;
    }

    private Order getOrder(String action, int size, String orderType,
            boolean GTC, int orderId) {
        Order order = new Order();
        if (GTC) {
            order.m_tif = "GTC";
        }
        order.m_action = action;
        order.m_totalQuantity = size;
        order.m_orderType = orderType;
        order.m_orderId = orderId;
        return order;
    }

    private Order getDefaultLimitOrder(String action, int size,
            String orderType, boolean GTC, double lmtPrice, int orderId) {
        Order order = new Order();
        if (GTC) {
            order.m_tif = "GTC";
        }
        order.m_action = action;
        order.m_totalQuantity = size;
        order.m_orderType = orderType;
        order.m_orderId = orderId;
        return order;
    }

    public void closeConnection() {
        EClientSocket client = ibAPI.getClient();
        client.eDisconnect();
    }
}